#include "DateTime.h"
#include <cstdint>
#include <cstdio>
#include <ctime>
#include <iomanip>
#include <sstream>
#include <sys/time.h>
#include <sys/timeb.h>

namespace xncc {
namespace types {
    static constexpr char DEFAULT_FMT[] = "%04d-%02d-%02d_%02d:%02d:%02d";
    static constexpr char MILL_FMT[]    = "%04d-%02d-%02d_%02d:%02d:%02d.%03d";
    static constexpr char MICRO_FMT[]   = "%04d-%02d-%02d_%02d:%02d:%02d.%06d";
    static constexpr char NANO_FMT[]    = "%04d-%02d-%02d_%02d:%02d:%02d.%09d";
    DateTime::DateTime(int date, int64_t time) : julianDateNum(date), timeNum(time) {}

    DateTime::DateTime(int year, int month, int day, int hour, int minute, int second, int millis)
        : julianDateNum(julianDate(year, month, day)),
          timeNum(makeHMS(hour, minute, second, millis * PRECISION_FACTOR[3]))
    {
    }

    DateTime::DateTime(int year, int month, int day, int hour, int minute, int second, int fraction, int precision)
        : julianDateNum(julianDate(year, month, day))
    {
        int nanos = convertToNanos(fraction, precision);
        timeNum   = makeHMS(hour, minute, second, nanos);
    }

    DateTime DateTime::fromTm(const tm& tm, int fraction, int precision)
    {
        int nanos = convertToNanos(fraction, precision);
        return {julianDate(tm.tm_year + YEAR_1900, tm.tm_mon + 1, tm.tm_mday),
                makeHMS(tm.tm_hour, tm.tm_min, tm.tm_sec, nanos)};
    }

    DateTime DateTime::fromTm(const tm& tm, int millis)
    {
        return {julianDate(tm.tm_year + YEAR_1900, tm.tm_mon + 1, tm.tm_mday),
                makeHMS(tm.tm_hour, tm.tm_min, tm.tm_sec, millis * PRECISION_FACTOR[3])};
    }

    std::string DateTime::toString() const { return toStringWithMillSecond(); }
    std::string DateTime::toStringNoFraction() const { return toStringPrecision(0, DEFAULT_FMT); }
    std::string DateTime::toStringWithMillSecond() const { return toStringPrecision(3, MILL_FMT); }
    std::string DateTime::toStringWithMicroSecond() const { return toStringPrecision(6, MICRO_FMT); }
    std::string DateTime::toStringWithNanoSecond() const { return toStringPrecision(9, NANO_FMT); }

    std::string DateTime::toStringPrecision(int precision, const char* fmt) const
    {
        int year = 0, month = 0, day = 0;
        getYMD(year, month, day);
        int hour = 0, minute = 0, second = 0, fraction = 0;
        getHMS(hour, minute, second, fraction, precision);
        char buf[32] = {0};
        snprintf(buf, sizeof(buf), fmt, year, month, day, hour, minute, second, fraction);
        return std::string(buf);
    }

    void DateTime::getYMD(int& year, int& month, int& day) const
    {
        int a = julianDateNum + 32044;
        int b = (4 * a + 3) / 146097;
        int c = a - int((b * 146097) / 4);
        int d = (4 * c + 3) / 1461;
        int e = c - int((1461 * d) / 4);
        int m = (5 * e + 2) / 153;
        day   = e - int((153 * m + 2) / 5) + 1;
        month = m + 3 - 12 * int(m / 10);
        year  = b * 100 + d - 4800 + int(m / 10);
    }

    inline void DateTime::getHMS(int& hour, int& minute, int& second, int& millis) const
    {
        const int64_t ticks = (timeNum / NANOS_PER_SEC);
        hour                = static_cast<int>(ticks / SECONDS_PER_HOUR);
        minute              = static_cast<int>((ticks / SECONDS_PER_MIN) % MINUTES_PER_HOUR);
        second              = static_cast<int>(ticks % SECONDS_PER_MIN);
        millis              = getMillisecond();
    }

    inline void DateTime::getHMS(int& hour, int& minute, int& second, int& fraction, int precision) const
    {
        const int64_t ticks = static_cast<int>(timeNum / NANOS_PER_SEC);
        hour                = static_cast<int>(ticks / SECONDS_PER_HOUR);
        minute              = static_cast<int>((ticks / SECONDS_PER_MIN) % MINUTES_PER_HOUR);
        second              = static_cast<int>(ticks % SECONDS_PER_MIN);
        fraction            = getFraction(precision);
    }

    DateTime DateTime::nowUtc()
    {
        struct timeval tv;
        gettimeofday(&tv, 0);
        time_t    tv_sec = tv.tv_sec;
        struct tm tmp;
        struct tm result = *gmtime_r(&tv_sec, &tmp);
        return fromTm(result, static_cast<int>(tv.tv_usec), 6);
    }

    DateTime DateTime::nowLocal()
    {
        struct timeval tv;
        gettimeofday(&tv, 0);
        time_t    tv_sec = tv.tv_sec;
        struct tm tmp;
        struct tm result = *localtime_r(&tv_sec, &tmp);
        return fromTm(result, static_cast<int>(tv.tv_usec), 6);
    }
    std::string DateTime::readableTime(time_t t)
    {
        struct tm tm1 = {};
        localtime_r(&t, &tm1);

        std::ostringstream oss;
        oss << std::setw(4) << std::setfill('0') << (tm1.tm_year + YEAR_1900) << "-" << std::setw(2)
            << std::setfill('0') << (tm1.tm_mon + 1) << "-" << std::setw(2) << std::setfill('0') << tm1.tm_mday << " "
            << std::setw(2) << std::setfill('0') << tm1.tm_hour << ":" << std::setw(2) << std::setfill('0')
            << tm1.tm_min << ":" << std::setw(2) << std::setfill('0') << tm1.tm_sec;

        return oss.str();
    }
    int64_t DateTime::timeMicro()
    {
        struct timeval tv;
        gettimeofday(&tv, 0);
        return tv.tv_sec * 1000000 + tv.tv_usec;
    }

    int64_t DateTime::timeNano()
    {
        struct timespec tp;
        clock_gettime(CLOCK_REALTIME, &tp);
        return tp.tv_sec * PRECISION_FACTOR[0] + tp.tv_nsec;
    }

    int DateTime::julianDate(int year, int month, int day)
    {
        // 公历日期 转儒略日  计算公式 见 https://baike.baidu.com/item/%E5%84%92%E7%95%A5%E6%97%A5/693474?fr=aladdin
        int a = (14 - month) / 12;
        int y = year + 4800 - a;
        int m = month + 12 * a - 3;
        return (day + int((153 * m + 2) / 5) + y * 365 + int(y / 4) - int(y / 100) + int(y / 400) - 32045);
    }

    int64_t DateTime::makeHMS(int hour, int minute, int second, int nanos)
    {
        return NANOS_PER_SEC * (SECONDS_PER_HOUR * hour + SECONDS_PER_MIN * minute + second) + nanos;
    }

    /*
        fraction 小数部分
        precision 精度  微秒 就是6位     毫秒 精度是3位  变成 纳秒 * 1000 000
    */
    int DateTime::convertToNanos(int fraction, int precision)
    {
        int nanos = 0;
        switch (precision) {
            case 0: nanos = fraction * PRECISION_FACTOR[0]; break;
            case 1: nanos = fraction * PRECISION_FACTOR[1]; break;
            case 2: nanos = fraction * PRECISION_FACTOR[2]; break;
            case 3: nanos = fraction * PRECISION_FACTOR[3]; break;
            case 4: nanos = fraction * PRECISION_FACTOR[4]; break;
            case 5: nanos = fraction * PRECISION_FACTOR[5]; break;
            case 6: nanos = fraction * PRECISION_FACTOR[6]; break;
            case 7: nanos = fraction * PRECISION_FACTOR[7]; break;
            case 8: nanos = fraction * PRECISION_FACTOR[8]; break;
            case 9:
            default: nanos = fraction * PRECISION_FACTOR[9]; break;
        }
        return nanos;
    }
    /*
        precision 精度  微秒 就是6位
    */
    int DateTime::getFraction(int precision) const
    {
        switch (precision) {
            case 0: return (getNanosecond() / PRECISION_FACTOR[0]);
            case 1: return (getNanosecond() / PRECISION_FACTOR[1]);
            case 2: return (getNanosecond() / PRECISION_FACTOR[2]);
            case 3: return (getNanosecond() / PRECISION_FACTOR[3]);
            case 4: return (getNanosecond() / PRECISION_FACTOR[4]);
            case 5: return (getNanosecond() / PRECISION_FACTOR[5]);
            case 6: return (getNanosecond() / PRECISION_FACTOR[6]);
            case 7: return (getNanosecond() / PRECISION_FACTOR[7]);
            case 8: return (getNanosecond() / PRECISION_FACTOR[8]);
            case 9:
            default: return (getNanosecond() / PRECISION_FACTOR[9]);
        }
    }

    int UtcTimeStamp::convert(const std::string& value, UtcTimeStamp& utc_time)
    {
        struct tm t = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
        int       ms;
        int ret = std::sscanf(value.data(), "%04d-%02d-%02d_%02d:%02d:%02d.%03d", &t.tm_year, &t.tm_mon, &t.tm_mday,
                              &t.tm_hour, &t.tm_min, &t.tm_sec, &ms);
        --t.tm_mon;
        t.tm_year -= YEAR_1900;
        if (ret != 7) {
            return -1;
        }
        utc_time = UtcTimeStamp(&t, ms);
        return 0;
    }

    DateTime UtcTimeStamp::fromUtcTimeT(time_t t, int millis)
    {
        struct tm result;
        gmtime_r(&t, &result);
        return DateTime::fromTm(result, millis);
    }
    DateTime UtcTimeStamp::fromUtcTimeT(time_t t, int fraction, int precision)
    {
        struct tm result;
        gmtime_r(&t, &result);
        return DateTime::fromTm(result, fraction, precision);
    }

    LocalTimeStamp UtcTimeStamp::toLocal()
    {
        time_t t_num = (SECONDS_PER_DAY * (julianDateNum - JULIAN_19700101) + timeNum / NANOS_PER_SEC);
        return {t_num, static_cast<int>(timeNum % NANOS_PER_SEC), 9};
    }

    DateTime LocalTimeStamp::fromLocalTimeT(time_t t, int millis)
    {
        struct tm result;
        localtime_r(&t, &result);
        return DateTime::fromTm(result, millis);
    }

    DateTime LocalTimeStamp::fromLocalTimeT(time_t t, int fraction, int precision)
    {
        struct tm result;
        localtime_r(&t, &result);
        return DateTime::fromTm(result, fraction, precision);
    }

    int LocalTimeStamp::convert(const std::string& value, LocalTimeStamp& local_time)
    {
        struct tm t = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
        int       ms;
        int ret = std::sscanf(value.data(), "%04d-%02d-%02d_%02d:%02d:%02d.%03d", &t.tm_year, &t.tm_mon, &t.tm_mday,
                              &t.tm_hour, &t.tm_min, &t.tm_sec, &ms);
        --t.tm_mon;
        t.tm_year -= YEAR_1900;
        if (ret != 7) {
            return -1;
        }
        local_time = LocalTimeStamp(&t, ms);
        return 0;
    }

    UtcTimeStamp LocalTimeStamp::toUtc()
    {
        int       year, month, day;
        int       hour, minute, second, millis;
        struct tm result = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nullptr};
        getYMD(year, month, day);
        getHMS(hour, minute, second, millis);
        result.tm_year  = year - YEAR_1900;
        result.tm_mon   = month - 1;
        result.tm_mday  = day;
        result.tm_hour  = hour;
        result.tm_min   = minute;
        result.tm_sec   = second;
        result.tm_isdst = -1;
        // mktime 带有时区转换操作
        return {mktime(&result), static_cast<int>(timeNum % NANOS_PER_SEC), 9};
    }
}  // namespace types
}  // namespace xncc